lab0
CS3210 共安排了六个 lab,第一个 lab 就是跟着Rust By Example学习 rust 的基础知识,然后通过rustlings检验学习成果,课程要求大约花费 30~40 小时完成 Rust By Example 的前 18 章,还是挺吃时间的。
目前我对于 rust 的开发环境并不了解,为了防止因为版本不同造成错误,我通过课程提供的 rustup 脚本安装了开发环境,版本号 rustc 1.37.0-nightly (2019-06-30)
。
从目前学习的内容来看 rust 与 c 有很大的相似点,所以我准备以对比的方式记录 rust
-
rustc 是 rust 的编译器,rust 语言扩展名为
.rs
,rust 的入口函数为fn main(){}
-
rust 的注释语法和 c 相同,
//
是单行注释,/**/
是多行注释,不同的是 rust 的多行注释支持嵌套 -
rust 的标准输入输出库为
std::fmt
(还不知道为什么用宏实现),格式化输出同样使用包含转义字符的字符串,但语法与 c 不同,功能也更强大,给出一个例子printf("%02.3f%d", a, b)
print!("{first:02.3f}{second}", second=b, first=a)
-
结构体默认和 c 相同无法直接输出,但是可以通过实现
fmt::Display
来实现类似toString
的功能,也可以通过derive
自动实现。 -
rust 的数据类型也都是经典组合了,基础数据类型从 i8、u8 到 i128、u128,还有 f32、f64,四字节 char,bool,isize,usize;组合数据类型数组元组切片枚举结构体一应俱全
-
rust 的运算符和 c 一致,
+-*/%!|^&>><<
。 -
rust 的枚举实现了 c 语言枚举和共用体两个特性,既可以枚举又可以存储变量,类似 switch 的
match
语句也让枚举处理更加优美,也让枚举变成了一个非常强大的工具。 -
typedef 现在 rust 中以关键字
type
呈现,但我还没仔细分析有没有什么差异。 -
rust 中的变量声明时,默认为可以接受非静态值 的不可变变量,
mut
关键字使变量可变,const
关键字使变量仅能接受静态值,这与 c 的处理方式完全不同。 -
编译器会对未使用的变量抛出 warning,但以下划线开头的变量不会
-
rust 对变量作用域和二次声明提出了
shadow
的概念,简单来说就是let
语句会覆盖本代码块和外层代码块的同名变量,对覆盖前后的变量类型、可变性没有要求,在本代码块中被覆盖的变量无法恢复,退出本代码块后被覆盖的外层变量会恢复(就是个作用域问题)。在造成shadow
的let
语句中可以使用被覆盖变量的值。 -
rust 不提倡对变量先声明而不初始化,编译器会对可能的未初始化而使用的情况抛出 error
-
rust 不进行隐式类型转换,显式类型转换的关键字为
as
-
rust 的数值字面量可以用类型名修饰,比如
1u8
就是八位无符号整数 1。 -
在声明变量时可以使用
:
显式声明类型,但是文档对编译器的自动类型推断非常自信 -
std::convert::From
std::convert::TryFrom
可以实现自定义类型的类型转换函数,后者支持异常处理。fmt::Display
如上一篇中猜测的会自动实现to_string
函数 -
rust 将代码块作为表达式处理,而且代码块的反大括号后面必须有分号。代码块的返回值取决于最后一行代码,若最后一行没有分号则会返回最后一行的值,若有则返回
()
。所以代码块是可以用来赋值的,值得注意的是,下面的控制语句中的代码块同样适用这个规则 -
rust 很自然地删掉了 c 语言中的
switch
语法,提供了更复杂但也更好用的match
语法。除此之外还有常见的if-else
while
for-in
等。 -
if-else
大概是最好懂的语法了,除了布尔表达式不需要圆括号、代码块必须有大括号之外没有什么特别之处 -
while
的额外特性也不多,可以使用例如'label: while true { break 'label; }
的语法退出特定层级的循环。loop
就是while true
的语法糖 -
for-in
虽然和 c 有差别但和其他现代语言没有太大差别,举个例子for n in 1..101 {}
,但是令我比较迷惑的是这个用例for n in 1..=100 {}
会将原本的左闭右开区间变成左闭右闭区间,两个例子都是循环到 100,不太明白这是糖还是有别的目的。for-in
还支持迭代器,但是现在看不太懂等后面章节吧 -
match
的语法看得我头大,所以我决定直接上代码 -
if let
以我的理解就是一个特殊的if
语句,布尔表达式是一个let
语句,该语句会像match
一样解构类型,如果解构成功则执行代码块,失败则跳转到else
-
while let
同理,在布尔表达式中尝试解构但代码块从if let
的选择控制变成了循环控制 -
定义函数的关键词是
fn
,声明返回值的关键词是->
位于函数体前。给个例子fn foo(bar: i32) -> i32 {}
,和前文提到的代码块类似,函数体最后一行代码不加分号即视为返回值的表达式,在任意位置都可以通过return
关键词返回。 -
通过
impl
关键词可以为结构体添加函数,即方法(method),方法可以通过&self
和&mut self
参数获得对象上下文, 方法通过::
访问 -
lambda 表达式的语法比较特别,使用
||
包围参数表,函数体可以没有大括号{}
。相对于函数来说,lambda 表达式更加自由,能够自动获取环境中的变量。 -
lambda 表达式与其他语言类似,可以作为函数的参数和返回值出现。但一些高级用法涉及到了后面的章节,这里先跳过。
-
rust 中存在一种类型叫做空类型,它的特点是没有任何可能的值,无法实例化。返回值为空类型的函数永远不会返回,文中指出一个可能会用到这个特性的地方是与网络请求有关的函数。
-
模块(module)类似于 cpp 的命名空间,模块中可以存放任何内容,默认私有,通过
pub()
关键词可以声明成员公有及公有范围,成员通过self
访问本模块成员,通过super
访问外部模块成员。在外部通过::
访问模块内的公有成员,mod
关键词可以绑定其他文件的模块,use
关键词可以绑定模块成员到上下文。 -
rust 将编译单元称为
crate
,crate 是经过预处理后生成的,与源代码的结构不完全相同 -
cargo是 rust 官方的中心化包管理器, crates.io 是平台,和其他包管理器一样支持项目管理、依赖管理、自定义脚本,还支持单元测试。
-
attribute
类似于 c 语言的预处理器命令,但不同的是在 rust 中这部分由编译器处理,语法为#[]
和#![]
,功能包括但不限于提供 crate 元信息、配置编译选项、条件编译、标记单元测试、标记基准测试。
match 用例
let number = 13;
match number {
// Match a single value
1 => println!("One!"),
// Match several values
2 | 3 | 5 | 7 | 11 => println!("This is a prime"),
// Match an inclusive range
// Bind to `n` for the sequence of 13 ..= 19
n @ 13..=19 => println!("A teen of age {}", n),
// Handle the rest of cases
_ => println!("Ain't special"),
}
let triple = (0, -2, 3);
// A match block can destructure items in a variety of ways
match triple {
// A match guard can be added to filter the arm
(0, y, z) => if y == z => println!("These are twins"),
// Destructure the second and third elements
(0, y, z) => println!("First is `0`, `y` is {:?}, and `z` is {:?}", y, z),
(1, ..) => println!("First is `1` and the rest doesn't matter"),
// `..` can be the used ignore the rest of the tuple
_ => println!("It doesn't matter what they are"),
// `_` means don't bind the value to a variable
}